using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Web.UI;
using System.IO;
using System.Xml;
using System.Collections;

using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.WebControls;

namespace ProjectCompliance.WebParts
{
    /// <summary>
    /// Get a SharePoint list as a DataTable
    /// </summary>
    public class ListDataTable : IDisposable
    {

        #region CONSTRUCTORS & DISPOSERS

        /// <summary>
        /// SharePoint list with DataTable-style access
        /// </summary>
        /// <param name="SiteContext">Context of current site</param>
        /// <param name="ListName">Name of list</param>
        /// <param name="RootWeb">True to get list from site collection; false to get list from site.</param>
        public ListDataTable(SPContext SiteContext, string ListName, bool RootWeb)
        {
            mustDisposeSP = false;
            this.site = SiteContext.Site;
            if (RootWeb)
                this.web = this.site.RootWeb;
            else
                this.web = SiteContext.Web;
            this.web.AllowUnsafeUpdates = true;
            this.list = this.web.Lists[ListName];
            this.listName = ListName;
            numItems = 0;
        }


        /// <summary>
        /// Sharepoint list with DataTable-style access
        /// </summary>
        /// <param name="web">Web on which list resides</param>
        /// <param name="ListName">Name of list</param>
        public ListDataTable(SPWeb web, string ListName)
        {
            mustDisposeSP = false;
            this.site = web.Site;
            this.web = web;
            this.web.AllowUnsafeUpdates = true;
            this.list = this.web.Lists[ListName];
            this.listName = ListName;
            numItems = 0;
        }


        /// <summary>
        /// SharePoint list with DataTable-style access
        /// </summary>
        /// <param name="SiteUrl">URL of web on which list resides</param>
        /// <param name="ListName">Name of SharePoint list</param>
        public ListDataTable(string SiteUrl, string ListName)
        {
            mustDisposeSP = true;
            this.site = new SPSite(SiteUrl);
            this.web = this.site.OpenWeb();
            this.web.AllowUnsafeUpdates = true;
            this.list = this.web.Lists[ListName];
            this.listName = ListName;
            numItems = 0;
        }


        /// <summary>
        /// Call Dispose() when finished with this object; important to clean up underlying SharePoint objects and prevent memory leak.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        public virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    if (mustDisposeSP)
                    {
                        if (this.site != null) this.site.Dispose();
                        if (this.web != null) this.web.Dispose();
                    }
                    if (ListTable != null) ListTable.Dispose();
                }
            }
            disposed = true;
        }

        #endregion


        #region FIELDS

        /// <summary>
        /// The SharePoint site containing the ListDataTable's list
        /// </summary>
        protected SPSite site;
        
        /// <summary>
        /// The SharePoint web containing the ListDataTable's list
        /// </summary>
        protected SPWeb web;
        
        /// <summary>
        /// The ListDataTable's list
        /// </summary>
        protected SPList list;
        
        /// <summary>
        /// The ListDataTable's table
        /// </summary>
        protected DataTable ListTable;

        /// <summary>
        /// List of columns that can be updated
        /// </summary>
        protected ArrayList UpdatableColumns;

        /// <summary>
        /// The current item to be returned from the ListDataTable
        /// </summary>
        protected int curItem;
        
        /// <summary>
        /// The total number of items in the ListDataTable
        /// </summary>
        protected int numItems;
        
        /// <summary>
        /// The name of the ListDataTable's SharePoint list
        /// </summary>
        protected string listName;

        /// <summary>
        /// Flag whether the SharePoint objects must be disposed
        /// </summary>
        protected bool mustDisposeSP;
        
        /// <summary>
        /// Flag whether the objec has been disposed
        /// </summary>
        protected bool disposed;

        /// <summary>
        /// Exception message for calling when not disposed
        /// </summary>
        protected const string disposedException = "Object has been disposed; no further operations allowed.";

        #endregion


        #region PROPERTIES

        /// <summary>
        /// Get the underlying table containing the SharePoint list data.
        /// </summary>
        public DataTable Table
        {
            get
            {
                return ListTable;
            }
        }


        /// <summary>
        /// Get the total number of items in the table
        /// </summary>
        public int ItemCount
        {
            get
            {
                return numItems;
            }
        }

        #endregion


        #region METHODS

        /// <summary>
        /// Load the list table from the SharePoint list
        /// </summary>
        public void Load()
        {
            SPListItem blankItem;
            bool removeBlank = false;

            if (!disposed)
            {
                if (this.list.Items.Count == 0)
                {
                    // If this.list has no items, then we can't create a ListTable correctly,
                    // because Items.GetDataTable() returns null.  The next idea was to create an
                    // empty table and read the list's schema  into it, but the schema from
                    // this.list.Items.XmlDataSchema doesn't seem to be in the correct format for
                    // ListTable.ReadXmlSchema.
                    // As a workaround for this case, we'll add a blank item to the list so that 
                    // we can proceed to get the schema using GetDataTable.
                    blankItem = this.list.Items.Add();
                    blankItem["Title"] = "Blank Item";
                    blankItem.Update();
                    this.list.Update();
                    removeBlank = true;
                }

                // Load the list into the table
                // Note, GetDataTable() will NOT load hidden fields.
                ListTable = this.list.Items.GetDataTable();

                // Get a list of which columns can be updated
                UpdatableColumns = new ArrayList();
                foreach (DataColumn column in ListTable.Columns)
                {
                    if (IsUpdatable(column))
                        UpdatableColumns.Add(column);
                }

                if (removeBlank)
                {
                    this.list.Items[0].Delete(); // Will always be the zeroth item
                    this.list.Update();
                    ListTable.Rows[0].Delete(); // Don't need it in ListTable either
                }

                if (ListTable != null)
                {
                    ListTable.AcceptChanges();
                    numItems = ListTable.Rows.Count;
                }
                else
                {
                    numItems = 0;
                }

                curItem = -1;
            }
            else
            {
                throw new ApplicationException(disposedException);
            }
        }


        /// <summary>
        /// Determine if a given column is valid for a SPListItem.Update() operation.
        /// Update may throw an exception if an SPListItem contains values for certain columns.
        /// </summary>
        /// <param name="column"></param>
        /// <returns></returns>
        private bool IsUpdatable(DataColumn column)
        {
            string ColumnName = column.ColumnName;

            if (column.ReadOnly) return false;
            if (ColumnName == "ID") return false;
            if (ColumnName == "Attachments") return false;
            if (ColumnName == "DocIcon") return false;
            if (ColumnName == "_UIVersionString") return false;
            if (ColumnName == "LinkTitle") return false;
            if (ColumnName == "LinkTitleNoMenu") return false;
            if (ColumnName == "Author") return false;
            if (ColumnName == "Editor") return false;
            if (ColumnName == "Modified") return false;
            if (ColumnName == "Created") return false;
            if (ColumnName == "ContentType") return false;

            return true;
        }

       
        /// <summary>
        /// Store changes made to the list table back to the SharePoint list
        /// </summary>
        public void Update()
        {
            SPListItem item;
            DataTable tableChanges;
            string columnName;
            int itemID;
            bool itemUpdated;

            if (!disposed)
            {
                // Additions
                itemUpdated = false;
                tableChanges = ListTable.GetChanges(DataRowState.Added);
                if (tableChanges != null)
                {
                    foreach (DataRow row in tableChanges.Rows)
                    {
                        item = this.list.Items.Add();
                        foreach (DataColumn column in this.UpdatableColumns)
                            {
                            columnName = column.ColumnName;
                            if (!(row[columnName] is System.DBNull))
                            {
                                item[columnName] = row[columnName];
                            }
                        }
                        item.Update();
                        itemUpdated = true;
                    }
                    if (itemUpdated)
                        this.list.Update();
                }

                // Deletions
                itemUpdated = false;
                tableChanges = ListTable.GetChanges(DataRowState.Deleted);
                if (tableChanges != null)
                {
                    foreach (DataRow row in tableChanges.Rows)
                    {
                        itemID = Util.GetInt(row, "ID", DataRowVersion.Original);
                        if (itemID > 0)
                        {
                            this.list.Items.DeleteItemById(itemID);
                            itemUpdated = true;
                        }
                    }
                    if (itemUpdated)
                        this.list.Update();
                }

                // Updates
                itemUpdated = false;
                tableChanges = ListTable.GetChanges(DataRowState.Modified);
                if (tableChanges != null)
                {
                    foreach (DataRow row in tableChanges.Rows)
                    {
                        itemID = Util.GetInt(row, "ID");
                        if (itemID > 0)
                        {
                            item = this.list.Items.GetItemById(itemID);
                            if (item != null)
                            {
                                foreach (DataColumn column in this.UpdatableColumns)
                                {
                                    columnName = column.ColumnName;
                                    if (!(row[columnName] is System.DBNull))
                                    {
                                        item[columnName] = row[columnName];
                                    }
                                }
                                item.Update();
                                itemUpdated = true;
                            }
                        }
                    }
                    if (itemUpdated)
                        this.list.Update();
                }

            }
            else
            {
                throw new ApplicationException(disposedException);
            }
        }


        /// <summary>
        /// Reset list table to the beginning
        /// </summary>
        public void ResetPosition()
        {
            if (!disposed)
            {
                curItem = -1;
            }
            else
            {
                throw new ApplicationException(disposedException);
            }
        }


        /// <summary>
        /// Position the list table at the next item
        /// </summary>
        /// <returns>true if another item may be returned via GetItem; false if no more items</returns>
        public Boolean Next()
        {
            if (!disposed)
            {
                curItem++;
                return (curItem < numItems);
            }
            else
            {
                throw new ApplicationException(disposedException);
            }
        }


        /// <summary>
        /// Get the current item in the list table
        /// </summary>
        /// <returns>DataTable row</returns>
        protected DataRow GetItem()
        {
            DataRow row = null;

            if (!disposed)
            {
                if (numItems <= 0)
                    throw new ApplicationException("No items are loaded");
                if (curItem == -1)
                    Next();
                if (curItem < numItems)
                {
                    row = ListTable.Rows[curItem];
                }
                return row;
            }
            else
            {
                throw new ApplicationException(disposedException);
            }

        }


        /// <summary>
        /// Append items to a StringBuilder for display (debugging)
        /// </summary>
        /// <param name="output">StringBuilder to which items will be appended</param>
        /// <param name="withSchema">Flag indicating whether schema should also be displayed</param>
        protected void AppendItems(ref StringBuilder output, bool withSchema)
        {
            StringWriter sw;

            if (!disposed)
            {
                // Write Rules

                if (ListTable != null)
                {
                    sw = new StringWriter();
                    if (withSchema)
                        ListTable.WriteXml(sw, XmlWriteMode.WriteSchema);
                    else
                        ListTable.WriteXml(sw, XmlWriteMode.IgnoreSchema);

                    output.Append("<PRE>");
                    output.Append(SPHttpUtility.HtmlEncode(sw.ToString()));
                    output.Append("</PRE>");
                }
                else
                {
                    output.Append("Table for list &quot;" + listName + "&quot; contains no items; make sure list has been loaded properly.");
                }
            }
            else
            {
                throw new ApplicationException(disposedException);
            }
        }

        #endregion
    }


}
